/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

#include "common.h"


using namespace Scenic::Renderer;


Texture *lineEdgeTexture = 0;
Texture *lineDashTexture = 0;


class LinePrimitives : public Primitives
{
	Texture *lineDashTexture;
public:
	LinePrimitives();

	void render();

	void setLineDashTexture(Texture *tex);
	Texture *getLineDashTexture() { return lineDashTexture; }
};

LinePrimitives::LinePrimitives()
{
	lineDashTexture = 0;
}

void LinePrimitives::setLineDashTexture(Texture *tex)
{
	setVertexType(tex ? VSF_TEX2 : VSF_TEX1);
	if(lineDashTexture)
		lineDashTexture->release();
	lineDashTexture = tex;
	if(lineDashTexture)
		lineDashTexture->addRef();
}

void LinePrimitives::render()
{
	Context *context = getContext();

	context->device->setTexture(0, lineEdgeTexture);
	context->device->setSamplerStates(0, FILTER_LINEAR, ADDRESS_CLAMP);
	context->device->setTextureStageModulation(0, 1);
	if(lineDashTexture)
	{
		context->device->setTexture(1, lineDashTexture);
		context->device->setSamplerStates(1, FILTER_LINEAR, ADDRESS_WRAP);
		context->device->setSamplerState(1, SAMPLER_MIP_FILTER, FILTER_POINT);
		context->device->setTextureStageModulation(1, 1);
	}
	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE_MINUS_SRC_ALPHA);

	getVertexStore()->draw();

	context->device->setTexture(1, NULL);
	context->device->setTexture(0, NULL);
	context->device->setSamplerState(1, SAMPLER_MIP_FILTER, FILTER_NONE);
}

LineRenderer::LineRenderer()
{
}


LinePrimitives *linePrimitives = new LinePrimitives(); 


void LineRenderer::begin(Context *context, bool isClosed)
{
	this->context = context;
	this->isClosed = isClosed;

	count = 0;
	color = context->color.getRGBA32();
	closeStroke = false;
	miterLimit = context->miterLimit;

	double det = sqrt(fabs(context->transform.m00 * context->transform.m11 - 
		context->transform.m01 * context->transform.m10));
	double a = det * context->lineWidth / 2.0;

	width = context->lineWidth / 2.0 * (a + 0.5) / a;
	minu = 0.25;
	maxu = 0.25 + (a + 0.5) / 2.0;

	
	
	if(lineEdgeTexture == 0)
		createEdgeTexture();
//	if(lineDashTexture == 0)
		createDashTexture();
	dashPhase = context->lineDashPhase;

	context->setPrimitives(linePrimitives);
	linePrimitives->setLineDashTexture(context->lineDashLengths.size > 0 ? lineDashTexture : 0);
	vs = context->primitives->getVertexStore();
	vs->setTransform(&context->transform);
}

void LineRenderer::end()
{
	if(isClosed && count > 1)
	{
		// TODO: better comparison
		if(fabs(firstp.x - prevp.x) > 0.001 || fabs(firstp.y - prevp.y) > 0.001)
			addPoint(firstp);
		drawJoin(firstp, prevd, firstd);
	}
	else if(!isClosed && count > 1)
	{
		drawCap(prevp, prevd);
	}
}

void LineRenderer::addPoint(const Double2 &p)
{
	count++;

	if(count == 1)
	{
		firstp = p;
		prevp = p;
	}
	else
	{
		double m;
		Double2 n;
		Double2 d;
		double length;

		n.x = p.x - prevp.x;
		n.y = p.y - prevp.y;

		length = sqrt(n.x * n.x + n.y * n.y);
		if(length == 0.0)
			return;

		m = width / length;
		d.x = -n.y * m;
		d.y = n.x * m;

		vs->setDefaultTex2(dashPhase, 0.5);
		if(count > 2)
			drawJoin(prevp, prevd, d);
		drawLine(prevp, p, d, length / context->lineWidth / dashLength);
		dashPhase += length / context->lineWidth / dashLength;
		dashPhase -= floor(dashPhase);
		vs->setDefaultTex2(dashPhase, 0.5);
		if(count == 2 && !isClosed)
		{
			Double2 d2;
	
			d2.x = -d.x;
			d2.y = -d.y;
			drawCap(prevp, d2);
		}
		prevp = p;
		prevd = d;
		if(count == 2)
			firstd = d;
	}
}


void LineRenderer::drawCap(const Double2 &p, const Double2 &d)
{
	if(context->lineCap == SQUARE_CAP)
		drawSquareCap(p, d);
	else if(context->lineCap == ROUND_CAP)
		drawRoundCap(p, d);
}

void LineRenderer::drawSquareCap(const Double2 &p, const Double2 &d)
{
	Double2 n;
	Double2 ds;
	double maxw;

	ds.x = d.x * context->transform.m00 + d.y * context->transform.m01;
	ds.y = d.x * context->transform.m10 + d.y * context->transform.m11;

	maxw = sqrt(ds.x * ds.x + ds.y * ds.y) / 2.0;

	n.x = d.y;
	n.y = -d.x;

	vs->addVertex(p.x, p.y, color, maxw, 0.0f);
	vs->addVertex(p.x - d.x, p.y - d.y, color, 0.0f, 0.0f);
	vs->addVertex(p.x - d.x + n.x, p.y - d.y + n.y, color, 0.0f, 0.0f);
	vs->addVertex(p.x + d.x + n.x, p.y + d.y + n.y, color, 0.0f, 0.0f);
	vs->addVertex(p.x + d.x, p.y + d.y, color, 0.0f, 0.0f);

	vs->addTriangle(0, 1, 2);
	vs->addTriangle(0, 2, 3);
	vs->addTriangle(0, 3, 4);

	vs->endBlock();
}

void LineRenderer::drawRoundCap(const Double2 &p, const Double2 &d)
{
	double startAngle, stopAngle;
	double dlength = sqrt(d.x * d.x + d.y * d.y);
	Double2 ds;
	double maxw;

	ds.x = d.x * context->transform.m00 + d.y * context->transform.m01;
	ds.y = d.x * context->transform.m10 + d.y * context->transform.m11;

	maxw = sqrt(ds.x * ds.x + ds.y * ds.y) / 2.0;
	
	startAngle = atan2(-d.y, -d.x);
	stopAngle = startAngle + M_PI;

	vs->addVertex(p.x, p.y, color, maxw, 0.0f);
	vs->addVertex(p.x - d.x, p.y - d.y, color, 0.0f, 0.0f);
	
	int nstep = (int)(M_PI / (M_PI * 2.0) * 50.0);
	int index = 1;
	
	for(int i = 0; i <= nstep; i++)
	{
		double angle = startAngle + (stopAngle - startAngle) * i / nstep;
		double x = cos(angle) * dlength;
		double y = sin(angle) * dlength;
		int a = vs->addVertex(p.x + x, p.y + y, color, 0.0f, 0.0f);
		
		vs->addTriangle(0, index, a);
		index = a;
	}

	vs->endBlock();
}

void LineRenderer::drawJoin(const Double2 &p, const Double2 &d1, const Double2 &d2)
{
	if(context->lineJoin == BEVEL_JOIN)
		drawBevelJoin(p, d1, d2);
	else if(context->lineJoin == MITER_JOIN)
		drawMiterJoin(p, d1, d2);
	else if(context->lineJoin == ROUND_JOIN)
		drawRoundJoin(p, d1, d2);  
}

void LineRenderer::drawBevelJoin(const Double2 &p, const Double2 &d1, const Double2 &d2)
{
	vs->addVertex(p.x, p.y, color, maxu, 0.0f);
	if(d1.x * d2.y - d1.y * d2.x > 0.0)
	{
		vs->addVertex(p.x - d1.x, p.y - d1.y, color, minu, 0.0f);
		vs->addVertex(p.x - d2.x, p.y - d2.y, color, minu, 0.0f);
	}
	else
	{
		vs->addVertex(p.x + d1.x, p.y + d1.y, color, minu, 0.0f);
		vs->addVertex(p.x + d2.x, p.y + d2.y, color, minu, 0.0f);
	}				
	vs->addTriangle(0, 1, 2);
	vs->endBlock();
}

void LineRenderer::drawMiterJoin(const Double2 &p, const Double2 &d1, const Double2 &d2)
{
	Double2 a;
	
	a.x = d1.x + d2.x;
	a.y = d1.y + d2.y;
	
	double l = sqrt(a.x * a.x + a.y * a.y);
	double w = width;	
	double m2 = 2.0 * w / l;
	
	if(m2 > miterLimit)
	{
		drawBevelJoin(p, d1, d2);
		return;
	}
	
	a.x = a.x / l * m2 * w;
	a.y = a.y / l * m2 * w;
	
	vs->addVertex(p.x, p.y, color, maxu, 0.0f);
	if(d1.x * d2.y - d1.y * d2.x > 0.0)
	{
		vs->addVertex(p.x - a.x, p.y - a.y, color, minu, 0.0f);
		vs->addVertex(p.x - d1.x, p.y - d1.y, color, minu, 0.0f);
		vs->addVertex(p.x - d2.x, p.y - d2.y, color, minu, 0.0f);
	}
	else
	{
		vs->addVertex(p.x + a.x, p.y + a.y, color, minu, 0.0f);
		vs->addVertex(p.x + d1.x, p.y + d1.y, color, minu, 0.0f);
		vs->addVertex(p.x + d2.x, p.y + d2.y, color, minu, 0.0f);
		
	}
	vs->addTriangle(0, 1, 2);
	vs->addTriangle(0, 1, 3);
	vs->endBlock();
}

void LineRenderer::drawRoundJoin(const Double2 &p, const Double2 &d1, const Double2 &d2)
{
	double startAngle, stopAngle;
	double dlength = sqrt(d1.x * d1.x + d1.y * d1.y);
	
	vs->addVertex(p.x, p.y, color, maxu, 0.0f);
	if(d1.x * d2.y - d1.y * d2.x > 0.0)
	{
		startAngle = atan2(-d1.y, -d1.x);
		stopAngle = atan2(-d2.y, -d2.x);
		vs->addVertex(p.x - d1.x, p.y - d1.y, color, minu, 0.0f);
		vs->addVertex(p.x - d2.x, p.y - d2.y, color, minu, 0.0f);
	}
	else
	{
		startAngle = atan2(d1.y, d1.x);
		stopAngle = atan2(d2.y, d2.x);
		vs->addVertex(p.x + d1.x, p.y + d1.y, color, minu, 0.0f);
		vs->addVertex(p.x + d2.x, p.y + d2.y, color, minu, 0.0f);
	}
	
	if(stopAngle > startAngle + M_PI)
		stopAngle -= M_PI * 2.0;
	else if(stopAngle < startAngle - M_PI)
		stopAngle += M_PI * 2.0;
	
	int nstep = (int)(fabs(stopAngle - startAngle) / (M_PI * 2.0) * 50.0);
	int index = 1;

//	printf("%lf %lf %d\n", startAngle / M_PI * 180.0, stopAngle / M_PI * 180.0, nstep);
	for(int i = 1; i < nstep; i++)
	{
		double angle = startAngle + (stopAngle - startAngle) * i / nstep;
		double x = cos(angle) * dlength;
		double y = sin(angle) * dlength;
		int a = vs->addVertex(p.x + x, p.y + y, color, minu, 0.0f);
		
		vs->addTriangle(0, index, a);
		index = a;
	}
	vs->addTriangle(0, index, 2);	
	
	vs->endBlock();
}

void LineRenderer::drawLine(const Double2 &p1, const Double2 &p2, const Double2 &d, double length)
{
	vs->addVertex(p1.x - d.x, p1.y - d.y, color, minu, 0.0f);
	vs->addVertex(p1.x, p1.y, color, maxu, 0.0f);
	vs->addVertex(p1.x + d.x, p1.y + d.y, color, minu, 0.0f);

	vs->setDefaultTex2(dashPhase + length, 0.5);

	vs->addVertex(p2.x - d.x, p2.y - d.y, color, minu, 0.0f);
	vs->addVertex(p2.x, p2.y, color, maxu, 0.0f);
	vs->addVertex(p2.x + d.x, p2.y + d.y, color, minu, 0.0f);

	vs->addTriangle(0, 3, 1);
	vs->addTriangle(1, 3, 4);
	vs->addTriangle(1, 4, 2);
	vs->addTriangle(2, 4, 5);

	vs->endBlock();
}


void LineRenderer::createEdgeTexture()
{
	lineEdgeTexture = new Texture(2, 1, FMT_A8R8G8B8, 0);
	int dest[2];

	dest[0] = 0x00000000;
	dest[1] = 0xffffffff;

	lineEdgeTexture->write(0, 0, 0, 2, 1, dest, 0, FMT_A8R8G8B8);
}

void LineRenderer::createDashTexture()
{
	if(context->lineDashLengths.size == 0)
	{
		dashLength = 1;
		return;
	}

	lineDashTexture = new Texture(2048, 1, FMT_A8R8G8B8, 0, 0);
	int dest[2048];
	double l = 0;
	int i, j = 0, k;
	double phase = 0;
	int state = 1;

	for(i = 0; i < context->lineDashLengths.size; i++)
		l += context->lineDashLengths[i];
	dashLength = l;

	for(i = 0; i < context->lineDashLengths.size; i++)
	{
		phase += context->lineDashLengths[i];
		k = (int)(phase / l * 2048);
		while(j < k)
			dest[j++] = state ? 0xffffffff : 0x0;
		state = 1 - state;
	}
	while(j < 2048)
		dest[j++] = 0xffffffff;

	lineDashTexture->write(0, 0, 0, 2048, 1, dest, 0, FMT_A8R8G8B8);
}
